package net.avcompris.binding;

import java.io.IOException;
import java.util.Map;

import net.avcompris.binding.annotation.XPath;

import com.avcompris.common.annotation.Nullable;

/**
 * extend this interface if you need bidirectional binding.
 * Essentially, if you have a type <tt>Xxx</tt> that needs to
 * inherit methods from this interface, you will make a
 * <tt>interface Xxx extends {@link Binding}&lt;Xxx&gt;</tt> to bring
 * correct types to the methods
 * such as {@link #clone()}, {@link followingSibling()}, etc.
 * Method names in the interface do not start with
 * <tt>getXxx()</tt>, <tt>setXxx()</tt>,
 * <tt>sizeOfXxx</tt>, <tt>isNullXxx()</tt>,
 * <tt>isXxx()</tt>, <tt>hasXxx()</tt>, <tt>doesXxx()</tt>,
 * <tt>toString()</tt> or <tt>toXxx()</tt>, to avoid conflicts with
 * custom methods bound to nodes via the {@link XPath} annotation.
 * 
 * @author David Andrianavalontsalama
 */
public interface Binding<T> {

	/**
	 * remove from the overall structure the underlying node 
	 * (for instance the <tt>org.w3c.dom.Node</tt> object is removed from the
	 * <tt>org.w3c.dom.Document</tt>) corresponding to this object.
	 * 
	 * @return the object itself.
	 */
	T remove();

	/**
	 * clone the underlying node correspoding to this object 
	 * (for instance the <tt>org.w3c.dom.Node</tt> object will be cloned).
	 * When returned, the copy is not bound to any node in the overall 
	 * structre, nor any of its children.
	 * 
	 * @return the copy.
	 */
	T clone();

	/**
	 * remove all attributes and children and text content in the underlying node.
	 * 
	 * @return the object itself.
	 */
	T clear();

	/**
	 * remove all attributes in the underlying node.
	 * 
	 * @return the object itself.
	 */
	T removeAttributes();

	/**
	 * remove all children in the underlying node.
	 * 
	 * @return the object itself.
	 */
	T removeChildren();

	/**
	 * remove all children of a given name in the underlying node.
	 * 
	 * @return the object itself.
	 */
	T removeChildren(String name);

	/**
	 * return the parent object, or <tt>null</tt> if the underlying node is the
	 * document root.
	 * 
	 * @param clazz the wished type for the requested object. 
	 * It must be an interface and able
	 * to be bound to the parent node of the original object, but it doesn't need to
	 * have a {@link XPath} annotation at its type's level.
	 */
	@Nullable
	<U> U parent(Class<U> clazz);

	/**
	 * return the parent object, or <tt>null</tt> if the underlying node is the
	 * document root, assuming the parent
	 * will of same type as this very object.
	 */
	@Nullable
	T parent();

	/**
	 * return the parent object, or <tt>null</tt> if the underlying node is the
	 * document root or if the parent has not the required name.
	 * 
	 * @param clazz the wished type for the requested object. 
	 * It must be an interface and able
	 * to be bound to the parent node of the original object, but it doesn't need to
	 * have a {@link XPath} annotation at its type's level.
	 */
	@Nullable
	<U> U parent(String name, Class<U> clazz);

	/**
	 * return the parent object, or <tt>null</tt> if the underlying node is the
	 * document root or if the parent has not the required name, assuming the parent
	 * will of same type as this very object.
	 */
	@Nullable
	T parent(String name);

	/**
	 * return the root object, or <tt>null</tt> if the underlying node of no
	 * document.
	 * 
	 * @param clazz the wished type for the requested object. 
	 * It must be an interface and able
	 * to be bound to the root node of the original object, but it doesn't need to
	 * have a {@link XPath} annotation at its type's level.
	 */
	@Nullable
	<U> U root(Class<U> clazz);

	/**
	 * return the root object, or <tt>null</tt> if the underlying node of no
	 * document, assuming the root
	 * will of same type as this very object.
	 */
	@Nullable
	T root();

	/**
	 * return the root object, or <tt>null</tt> if the underlying node of no
	 * document or if the root node has not the required name.
	 * 
	 * @param clazz the wished type for the requested object. 
	 * It must be an interface and able
	 * to be bound to the root node of the original object, but it doesn't need to
	 * have a {@link XPath} annotation at its type's level.
	 */
	@Nullable
	<U> U root(String name, Class<U> clazz);

	/**
	 * return the root object, or <tt>null</tt> if the underlying node of no
	 * document or if the root node has not the required name, assuming the root
	 * will of same type as this very object.
	 */
	@Nullable
	T root(String name);

	/**
	 * return the preceding sibling of this object, or <tt>null</tt> if the
	 * underlying node is the first child of its parent.
	 * 
	 * @param clazz the wished type for the requested object. 
	 * It must be an interface and able
	 * to be bound to the same node as the original object, but it doesn't need to
	 * have a {@link XPath} annotation at its type's level.
	 */
	@Nullable
	<U> U precedingSibling(Class<U> clazz);

	/**
	 * return the preceding sibling of this object, or <tt>null</tt> if the
	 * underlying node is the first child of its parent, assuming the sibling
	 * will of same
	 * type as this very object.
	 */
	@Nullable
	T precedingSibling();

	/**
	 * return the preceding sibling, of a given name, of this object, or <tt>null</tt> if the
	 * underlying node is the first child of its parent.
	 * 
	 * @param clazz the wished type for the requested object. 
	 * It must be an interface and able
	 * to be bound to the same node as the original object, but it doesn't need to
	 * have a {@link XPath} annotation at its type's level.
	 */
	@Nullable
	<U> U precedingSibling(String name, Class<U> clazz);

	/**
	 * return the preceding sibling, of a given name, of this object, or <tt>null</tt> if the
	 * underlying node is the first child of its parent, assuming the sibling
	 * will of same
	 * type as this very object.
	 */
	@Nullable
	T precedingSibling(String name);

	/**
	 * return the following sibling of this object, or <tt>null</tt> if the
	 * underlying node is the last child of its parent.
	 * 
	 * @param clazz the wished type for the requested object. 
	 * It must be an interface and able
	 * to be bound to the same node as the original object, but it doesn't need to
	 * have a {@link XPath} annotation at its type's level.
	 */
	@Nullable
	<U> U followingSibling(Class<U> clazz);

	/**
	 * return the following sibling, of a given name, of this object, or <tt>null</tt> if the
	 * underlying node is the last child of its parent.
	 * 
	 * @param clazz the wished type for the requested object. 
	 * It must be an interface and able
	 * to be bound to the same node as the original object, but it doesn't need to
	 * have a {@link XPath} annotation at its type's level.
	 */
	@Nullable
	<U> U followingSibling(String name,Class<U> clazz);

	/**
	 * return the following sibling of this object, or <tt>null</tt> if the
	 * underlying node is the last child of its parent, assuming the sibling
	 * will of same
	 * type as this very object.
	 */
	@Nullable
	T followingSibling();

	/**
	 * get the first child of this object, that is of its the underlying
	 * node.
	 * 
	 * @param clazz the wished type for the requested object. 
	 * It must be an interface and able
	 * to be bound to the child node of the original object, but it doesn't need to
	 * have a {@link XPath} annotation at its type's level.
	 */
	<U> U firstChild(Class<U> clazz);

	/**
	 * return the following sibling, of a given name, of this object, or <tt>null</tt> if the
	 * underlying node is the last child of its parent, assuming the sibling
	 * will of same
	 * type as this very object.
	 */
	@Nullable
	T followingSibling(String name);

	/**
	 * get the first child of this object, that is of its the underlying
	 * node, assuming the child will be of same type as this very
	 * object.
	 */
	T firstChild();

	/**
	 * get the first child, of a given name, of this object, that is of its the underlying
	 * node.
	 * 
	 * @param clazz the wished type for the requested object. 
	 * It must be an interface and able
	 * to be bound to the child node of the original object, but it doesn't need to
	 * have a {@link XPath} annotation at its type's level.
	 */
	<U> U firstChild(String name, Class<U> clazz);

	/**
	 * get the first child, of a given name, of this object, that is of its the underlying
	 * node, assuming the child will be of same type as this very
	 * object.
	 */
	T firstChild(String name);

	/**
	 * get the last child of this object, that is of its underlying node.
	 * 
	 * @param clazz the wished type for the requested object. 
	 * It must be an interface and able
	 * to be bound to the child node of the original object, but it doesn't need to
	 * have a {@link XPath} annotation at its type's level.
	 */
	<U> U lastChild(Class<U> clazz);

	/**
	 * get the last child of this object, that is of its underlying node,
	 * assuming the child will be of same type as this very
	 * object.
	 */
	T lastChild();

	/**
	 * get the last child, of a given name, of this object, that is of its underlying node.
	 * 
	 * @param clazz the wished type for the requested object. 
	 * It must be an interface and able
	 * to be bound to the child node of the original object, but it doesn't need to
	 * have a {@link XPath} annotation at its type's level.
	 */
	<U> U lastChild(String name, Class<U> clazz);

	/**
	 * get the last child, of a given name, of this object, that is of its underlying node,
	 * assuming the child will be of same type as this very
	 * object.
	 */
	T lastChild(String name);

	/**
	 * get a given child of this object.
	 * 
	 * @param clazz the wished type for the requested object. 
	 * It must be an interface and able
	 * to be bound to the child node of the original object, but it doesn't need to
	 * have a {@link XPath} annotation at its type's level.
	 */
	<U> U child(int index, Class<U> clazz);

	/**
	 * get a given child of this object, 
	 * assuming the child will be of same type as this very
	 * object.
	 */
	T child(int index);

	/**
	 * get a given named child of this object.
	 * 
	 * @param clazz the wished type for the requested object. 
	 * It must be an interface and able
	 * to be bound to the child node of the original object, but it doesn't need to
	 * have a {@link XPath} annotation at its type's level.
	 */
	<U> U child(String name, int index, Class<U> clazz);

	/**
	 * get a given named child of this object, 
	 * assuming the child will be of same type as this very
	 * object.
	 */
	T child(String name, int index);

	/**
	 * get all children of this object.
	 * 
	 * @param clazz the wished type for the requested objects. 
	 * It must be an interface and able
	 * to be bound to the child nodes of the original object, but it doesn't need to
	 * have a {@link XPath} annotation at its type's level.
	 */
	<U> U[] children(Class<U> clazz);

	/**
	 * get all children of this object, 
	 * assuming the children will be of same type as this very
	 * object.
	 */
	T[] children();

	/**
	 * get all children, with a given name, of this object.
	 * 
	 * @param clazz the wished type for the requested objects. 
	 * It must be an interface and able
	 * to be bound to the child nodes of the original object, but it doesn't need to
	 * have a {@link XPath} annotation at its type's level.
	 */
	<U> U[] children(String name, Class<U> clazz);

	/**
	 * get all children, with a given name, of this object, 
	 * assuming the children will be of same type as this very
	 * object.
	 */
	T[] children(String name);

	/**
	 * return <tt>true</tt> if the object has no child.
	 */
	boolean noChild();

	/**
	 * return <tt>true</tt> if the object has no child of a given name.
	 */
	boolean noChild(String name);

	/**
	 * move this object before an object already in the overall structure
	 * (that is, its underlying node).
	 * 
	 * @param previous the object before which to move this object. It must be
	 * already bound in the overall structure.
	 */
	void moveBefore(Object next);

	/**
	 * move this object before after an object already in the overall structure
	 * (that is, its underlying node).
	 * 
	 * @param previous the object after which to move this object. It must be
	 * already bound in the overall structure.
	 */
	void moveAfter(Object previous);

	/**
	 * move this object in place of an object already in the overall structure
	 * (that is, its underlying node),
	 * and removing it.
	 * 
	 * @param object the object to replace, which is already bound in the 
	 * overall structure.
	 */
	void moveInLieuOf(Object object);

	/**
	 * move this object as a child of a node already in the overall structure,
	 * (that is, its underlying node).
	 * 
	 * @param object the parent to add a child to, which is already bound in the 
	 * overall structure.
	 */
	void moveBeforeFirstChildOf(Object object);

	/**
	 * move this object as a child of a node already in the overall structure,
	 * (that is, its underlying node).
	 * 
	 * @param object the parent to add a child to, which is already bound in the 
	 * overall structure.
	 */
	void moveAfterLastChildOf(Object object);

	/**
	 * rebind this object (that is, its underlying object) to another type.
	 * 
	 * @param clazz the new wished type. It must be an interface and able
	 * to be bound to the same node as the original object, but it doesn't need to
	 * have a {@link XPath} annotation at its type's level.
	 */
	<U> U self(Class<U> clazz);

	/**
	 * return this object. This method is there for symmetry
	 * with {@link #self(Class)}.
	 */
	T self();

	/**
	 * get the text content of this object's underlying node. If
	 * the node has children, the value returned is the concatenation of all
	 * their texts. That is why its name is different than the name of
	 * the {@link resetContent(String)} method, which deletes all existing
	 * non-text children.
	 */
	String textContent();

	/**
	 * set the text content of this object's underlying node, deleting all
	 * non-text children if there were any.
	 * 
	 * @return the object itself.
	 */
	T resetContent(String content);

	/**
	 * get the name of this object's underlying node.
	 */
	String name();

	/**
	 * set the name of this object's underlying node.
	 * 
	 * @return the object itself.
	 */
	T rename(String name);

	/**
	 * get the namespace URI of this object's underlying node.
	 */
	String namespaceURI();

	/**
	 * get the type of this object's underlying node.
	 */
	String type(); // TODO return an Enum instead of a String

	/**
	 * get the attributes of this object's underlying node.
	 */
	Map<String, Object> attributes();

	/**
	 * get an attribute value from this object's underlying node.
	 */
	Object attribute(String name);

	/**
	 * set an attribute of this object's underlying node.
	 * 
	 * @param value the attribute's value, or <tt>null</tt> if the intent
	 * is to remove the attribute.
	 * @return the object itself.
	 */
	T attribute(String name, @Nullable Object value);

	/**
	 * serialize to the default serializer. If no default serializer has been
	 * specified, throw an {@link IllegalStateException}.
	 */
	void serialize() throws IOException;

	/**
	 * serialize to the specific serializer.
	 */
	void serializeUsing(Serializer<?> serializer) throws IOException;

	/**
	 * set a default serializer.
	 * 
	 * @return the object itself.
	 */
	T useSerializer(Serializer<?> serializer);

	/**
	 * return the default serializer, or <tt>null</tt> if none was specified.
	 */
	@Nullable
	Serializer<?> serializer();
	
	/**
	 * return the underlying object selected by XPath, for instance 
	 * a {@link org.w3c.dom.Node} if we are working with DOM.
	 */
	Object node();
	
	/**
	 * bind a new object by evaluating an XPath expression, starting on this 
	 * very node, and returning an object of the same type as this very one.
	 * 
	 * @param args the arguments to be passed as variables to the XPath expression
	 */
	T evaluate(String xpathExpression, Object... args);
	
	/**
	 * bind a new object by evaluating an XPath expression, starting on this 
	 * very node, and returning an object of the given type.
	 * 
	 * @param clazz the new wished type. It must be an interface and able
	 * to be bound to the node that will be selected by the XPath expression, 
	 * but it doesn't need to
	 * have a {@link XPath} annotation at its type's level.
	 * @param args the arguments to be passed as variables to the XPath expression
	 */
	<U> U evaluate(Class<U> clazz, String xpathExpression, Object... args);
	
	/**
	 * bind a new object by evaluating an XPath expression, functions, etc.
	 * starting on this 
	 * very node, and returning an object of the same type as this very one.
	 * 
	 * @param args the arguments to be passed as variables to the XPath expression
	 */
	T evaluate(XPath xpath, Object... args);
	
	/**
	 * bind a new object by evaluating an XPath expression, starting on this 
	 * very node, and returning an object of the given type.
	 * 
	 * @param clazz the new wished type. It must be an interface and able
	 * to be bound to the node that will be selected by the XPath expression, 
	 * but it doesn't need to
	 * have a {@link XPath} annotation at its type's level.
	 * @param args the arguments to be passed as variables to the XPath expression
	 */
	<U> U evaluate(Class<U> clazz, XPath xpath, Object... args);
}
